/* Includes */
#include "dirent.h"
#include "stdio.h"
#include "surflib.h"
#include "showmany.h"
#include <omp.h>
#include <ctime>
#include <fstream>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

using namespace std;

/* Define principali */
#define IN_FOLDER "imgs/"
#define OUT_FOLDER "res/"
#define MAX_VALID_PATTERN 5 //10? Da valutare

/* Prototipi funzioni principali */
bool parallel_setup(vector<string>&);
void read_main_descriptors(vector<string>);
void get_patterns(vector<string>, vector<string>&);
void smart_match(vector<string>, vector<string>, vector< vector<string> >&);
void display_results(vector< vector<string> >);

/*
*	Funzione main
*/
int main() {
	/* Array di tutte le immagini (ordinate per punteggio) */
	vector<string> test;
	
	/* Array delle immagini da usare come pattern */
	vector<string> patt;
	
	/* Array contenente i cluster risultanti (ordinati per lunghezza) */
	vector< vector<string> > result;
	
	clock_t start = clock();
	
	/* The magic happens */
	if(!parallel_setup(test))
		clock_t end = clock();	
	else {
		read_main_descriptors(test);
	
		cout << endl;	
		get_patterns(test,patt);	
		smart_match(test,patt,result);
	
		clock_t end = clock();	
  		cout<< "[OpenSURF] Tempo totale: " << float(end - start) / CLOCKS_PER_SEC  << " secondi" << endl;
		
		display_results(result);		
	}

	/* The magic ends */
	return 0;
}

/* Implementazione funzioni */

/*
*	Legge i files nella cartella e crea l'array principale
*/
bool parallel_setup(vector<string> &A) {
	DIR *pDIR; 
	struct dirent *entry;
	int num_elem=0;
	int elem[20000]; // Max 20k foto
		
	cout << "[SYSTEM] parallel_setup()" << endl;
	
	if ( pDIR=opendir(IN_FOLDER) ) {
		while( entry=readdir(pDIR) ) {
			
			/* non considero . e ..  e .svn e .DS_Store (mac)*/
			if( strcmp(entry->d_name,".") != 0 && strcmp(entry->d_name,"..") != 0 && strcmp(entry->d_name,".svn") != 0 && strcmp(entry->d_name,".DS_Store") != 0){
				num_elem++;
				
				cout << "[SYSTEM] Letto "<< entry->d_name << endl;
				
				A.push_back(entry->d_name);		
			}
		}
	}else {
		closedir(pDIR);	
		cout << "[SYSTEM] Apertura cartella fallita" << endl;
		return 0;
	}
	closedir(pDIR);
	return 1;
}

/*
*	Legge i files nell'array e crea i rispettivi descrittori
*/
void read_main_descriptors(vector<string> A) {

	int num_elem = A.size();
	//vector<string>::iterator it = A.begin();
	
	cout << "[OpenSURF] read_main_descriptors() " << endl;
	
	#pragma omp parallel
	{
		#pragma omp for 
		for(int i=0; i<num_elem; i++) {				
			string img_path = IN_FOLDER;
			img_path.append(A[i]);
			IplImage *img=cvLoadImage((char*)img_path.c_str());
			
			if(img) {
				/* leggo lo stream in input, genero lo stream di output */
				ifstream ifs((char*)img_path.c_str(), ios::in | ios::binary);
				string out_name = OUT_FOLDER;
				out_name.append(A[i]);

				/* copio (da rivedere, non funge con le immagini) 
				ofstream ofs(out_name, ios::out | ios::binary);
				ofs << ifs.rdbuf(); */

				/* salvo il descrittore */
				out_name.append(".txt");
				cout << "[OpenSURF] Descrittore "<< out_name << endl;

				IpVec ipts;
				
				/* Applicare qualche filtro qui magari? */	
				surfDetDes(img, ipts, false, 5, 4, 2, 0.0004f);
				cout<< "[OpenSURF] Trovati " << ipts.size() << " punti di interesse." << endl; 
				saveSurf((char*)out_name.c_str(),ipts);
					
				/* inserisco in A in ordine */
				/*int index2;
				cout << "[SYSTEM] Letti " << num_elem << " elementi" <<endl;
				for(int index=1; index<num_elem; index++)
					index2 = index - 1;
					while(elem[index2]<ipts.size() && index2>=0){
						elem[index2+1] = elem[index2];
						index2--;
					}
					elem[index2+1] = ipts.size();
					cout << "Inserisco " << entry->d_name << " in " << index2+1 <<endl;
					A.insert(it,index2+1,entry->d_name);
					it = A.begin();
				*/
			
				out_name.clear();
				cout << endl ;
			}
			else
				cout << "[OpenSURF] Immagine non valida -> skip " << endl;
			img_path.clear();
		}
	}	
}

/*
*	Crea un array pattern dai 5 migliori descrittori dell'array principale
*	Ordina l'array risultante in base alla vicinanza dei match delle immagini
*/
void get_patterns(vector<string> A,vector<string> &T) {
	cout << "[OpenSURF] get_patterns() " << endl;
	for(int i=0; i<MAX_VALID_PATTERN; i++) {
		T.push_back(A[i]);
	}
}

/*
*	Confronta ogni immagine della cartella con i 5 pattern ottenuti.
*	L'indice del pattern con cui matcha diventa l'indice del gruppo in cui viene inserita l'immagine
*	L'array dei gruppi viene ordinato in base al numero di match contenuti
*/
void smart_match(vector<string> A,vector<string> P,vector< vector<string> > &R) {
	cout << "[OpenSURF] smart_match() " << endl;

	IpVec ipt_pattern, ipt_tested;
	IpPairVec matches;
	
	
	for(int i=0; i<MAX_VALID_PATTERN; i++) { // MAX_VALID_PATTERN * TOTAL confronti (minore di O(n^2), ma stessa classe)
		/* Il pattern va nel risultato */
		
		/* Immagine pattern attuale (carico il descrittore)*/
		string tmp = OUT_FOLDER;
		tmp.append(P[i]);
		tmp.append(".txt");
		loadSurf((char*)tmp.c_str(), ipt_pattern);
		tmp.clear();
		
		vector<string> sub;
		sub.push_back(P[i]);
		
		for(int j=0; j < A.size(); j++) {
			/* Immagine testata attualmente (carico il descrittore)*/
			if(A[j] != P[i]) {
				string tmp = OUT_FOLDER;
				tmp.append(A[j]);
				tmp.append(".txt");
				loadSurf((char*)tmp.c_str(), ipt_tested);
				tmp.clear();
			
				/* Get dei match */
				/* che sia da fare trasformazioni prima? */
				getMatches(ipt_pattern,ipt_tested,matches);
				cout<< "[OpenSURF] Matches: " << matches.size() << " " ;
			
				if(matches.size() > 0 /* 3 o 5? */) {
					cout << "-> Le due immagini matchano " << endl;
					sub.push_back(A[j]);
				} 
				else 
					cout << endl;
			}
		}
		
		R.push_back(sub);
		sub.clear();
	}
}

/*
*	Mostra i gruppi di immagini omogenee
*/
void display_results(vector< vector<string> > T){
	IplImage *img1, *img2;
	cout << "[OpenSURF] display_results()" <<endl;
	for(int i=0; i<T.size(); i++){
		for(int j=0; j<T[i].size(); j++){
			
			/* Leggo e carico le due immagini */
			string path = IN_FOLDER;
			path.append(T[i][0]);
			
			img1 = cvLoadImage((char*)path.c_str());
			path.clear();
			
			path = IN_FOLDER;
			path.append(T[i][j]);
			
			img2 = cvLoadImage((char*)path.c_str());
			path.clear();
			
			/* Da migliorare: leggere i descrittori che ho gia */
			IpVec ipts1, ipts2;
		  	surfDetDes(img1,ipts1,false,4,4,2,0.0001f);
		  	surfDetDes(img2,ipts2,false,4,4,2,0.0001f);

		  	IpPairVec matches;
		  	getMatches(ipts1,ipts2,matches);

			/* Visualizzo le immagini matchate */
		  		for (int k = 0; k < matches.size(); ++k) {
		    		drawPoint(img1,matches[k].first);
		    		drawPoint(img2,matches[k].second);

		    		const int & w = img1->width;
		    		cvLine(img1,cvPoint(matches[k].first.x,matches[k].first.y),cvPoint(matches[k].second.x+w,matches[k].second.y), cvScalar(255,255,255),1);
		    		cvLine(img2,cvPoint(matches[k].first.x-w,matches[k].first.y),cvPoint(matches[k].second.x,matches[k].second.y), cvScalar(255,255,255),1);
		  		}
		
			cvShowManyImages("Immagini",2,img1,img2);
		}
	}
}